/**
 * 
 */
package com.impress.service;


import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;


import com.impress.GoogleAccount;
import com.impress.Utility;
import com.impress.Utility.HttpForbiddenException;
import com.impress.Utility.HttpUnauthorizedException;
import com.impress.Utility.InvalidTokenException;
import com.impress.model.AccountPreference;
import com.impress.model.Feed;
import com.impress.model.Item;
import com.impress.model.Label;
import com.impress.model.SettingsPreference;
import com.impress.provider.FeedData;

/**
 * Item 服务类
 * @author pluto
 *
 */
public class ItemService {


	private static final String TAG = "ItemService";
	private Context context;
	
	public ItemService(){
		super();
		context = null;
	}
	
	public ItemService (Context context){
		this.context = context;
	}
	
	/**
	 * 获得最新的number条未读条目(contains some duplicate items, to be solved.)
	 * <br>
	 * API为:google.com/reader/api/0/stream/contents/user/-/state/com.google/reading-list?
	 * <br>
	 * xt=user/-/state/com.google/read  排除的条目状态
	 * <br>
	 * n=number 需要抓取的条目数
	 * <br>
	 * ot=timestamp 比这个时间更老的条目将不会被抓取
	 * <br>
	 * ck=timestamp 现在时间戳，防止抓取缓存条目
	 * <br>
	 * client=scroll 鉴别客户端(未知用途)
	 * <br>
	 * r=n item的返回顺序，d或者n是默认的日期降序排，o是日期升序排.
	 * @param number how many items we want to fetch
	 * @param ot we need to fetch items all after this time
	 * @return List items counts may be less than number passed in.
	 * @throws HttpForbiddenException 
	 * @throws HttpUnauthorizedException
	 * @throws ConnectTimeOutException
	 * @throws SocketTimeOutException
	 * @throws JSONException
	 */
	public void fetchNewItems(int number, long ot) throws Exception{
		/*initial parameters */
		String url = Utility.CONTENT_URL + Utility.ITEM_READINGLIST_STATE;
		String xtString = Utility.ITEM_READ_STATE;
		String nString = number + "";
		String ckString = System.currentTimeMillis() + "";
		String otString = ot + "";
		String clientString = "scroll";
		String rString = "n";
		String queryString = Utility.makeupParameter(Utility.PARAMETER_CK,ckString,Utility.PARAMETER_XT,
				xtString,Utility.PARAMETER_N,nString,Utility.PARAMETER_OT,otString,Utility.PARAMETER_R,
				rString,Utility.PARAMETER_CLIENT,clientString);
		String auth = getAuth();
		String responseString = "";
		try{
			responseString = GDataClient.doGet(url, queryString, auth);			
		} 
		catch (Exception e) {
			if (e instanceof HttpUnauthorizedException) {
				auth = getANewAuth();
				responseString = GDataClient.doGet(url, queryString, auth);
			}else {
				throw e;
			}
		}
		/*parse json result to entity info.*/
		JSONObject jsonContent = new JSONObject(responseString);
		SetLatestItemTimestamp(Long.valueOf(jsonContent.getString(Utility.JSON_NODE_UPDATED)));
		JSONArray itemsJsonArray = jsonContent.getJSONArray(Utility.JSON_NODE_ITEMS);
		int itemsCount = itemsJsonArray.length();
		for(int i = 0; i<itemsCount; i++){
			JSONObject itemJsonObject = itemsJsonArray.getJSONObject(i);
			
			GetItem(itemJsonObject);
		}
	}

	/**
	 * fetch new items using timestamp in preference.
	 * @param number
	 * @return
	 * @throws Exception
	 */
	public void fetchNewItems(int number) throws Exception{
		fetchNewItems(number, GetLatestItemTimestamp());
	}
	
	
	/**
	 * fetch one new item by post the item id to "stream/items/contents"
	 * @param itemId the long value of the item id.
	 * @return
	 * @throws Exception
	 */
	public void fetchNewItems(String itemId)throws Exception{
		String url = Utility.CONTENT_ITEM_URL;
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair(Utility.PARAMETER_I, itemId));
		String auth = getAuth();
		String responseString = "";
		try {
			responseString = GDataClient.makePost(url, params, auth);
			JSONObject jsonContent = new JSONObject(responseString);
			JSONArray itemsArray = jsonContent.getJSONArray(Utility.JSON_NODE_ITEMS);
			JSONObject itemJsonObject = itemsArray.getJSONObject(0);
			GetItem(itemJsonObject);
		} catch (Exception e) {
			if (e instanceof HttpUnauthorizedException) {
				auth = getANewAuth();
				responseString = GDataClient.makePost(url, params, auth);
			}else {
				throw e;
			}
		}
	}
	
	
	/**
	 * fetch a list of items by post item ids to "stream/items/contents"
	 * @param itemIds a set of item ids.
	 * @return
	 * @throws Exception
	 */
	public void fetchNewItems(Set<Long> itemIds) throws Exception{
		String url = Utility.CONTENT_ITEM_URL;
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		for (Long itemId : itemIds) {
			params.add(new BasicNameValuePair(Utility.PARAMETER_I, itemId.toString()));
		}
		String auth = getAuth();
		String responseString = "";
		try {
			responseString = GDataClient.makePost(url, params, auth);
			
		} catch (Exception e) {
			if (e instanceof HttpUnauthorizedException) {
				auth = getANewAuth();
				responseString = GDataClient.makePost(url, params, auth);
			}else {
				throw e;
			}
		}
		JSONObject jsonContent = new JSONObject(responseString);
		JSONArray itemArray = jsonContent.getJSONArray(Utility.JSON_NODE_ITEMS);
		int count = itemArray.length();
		for (int i = 0; i < count; i++) {
			JSONObject itemJsonObject = itemArray.getJSONObject(i);
			GetItem(itemJsonObject);
		}
	}
	
	/**
	 * Parse item info. in json results, and save it to db.
	 * @param itemJsonObject json object to be parsed.
	 * @return item object.
	 */
	private void GetItem(JSONObject itemJsonObject) throws JSONException{
		Item item = new Item();
		try {
			item.setAtomID(itemJsonObject.getString(Utility.JSON_NODE_ID));	//set item atomID
			/* set category, category node contains an array of strings.*/
			JSONArray categoryArray = itemJsonObject.getJSONArray(Utility.JSON_NODE_CATEGORY);
			for(int i=0;i<categoryArray.length();i++){
				String categoryString = categoryArray.getString(i);
				/*strings like "user/23828382328/label/labelname" are we need.*/
				
				String[] splitStrings = categoryString.split("/");
				int length = splitStrings.length;
				if(!TextUtils.equals("user", splitStrings[0]))
					break;
				if(length == 4 && TextUtils.equals(splitStrings[2], "label")){ //set label
					categoryString = splitStrings[3];
					item.categories.add(categoryString);
				}else if(length == 5 && TextUtils.equals(splitStrings[2], "state")){ //set state.
					item.setState(splitStrings[4]);
				}
			}
			
			item.initStatePending();
			
			item.setTitle(itemJsonObject.getString(Utility.JSON_NODE_TITLE)); //set title
		
			item.setUpdateTimestamp(Long.valueOf(itemJsonObject.getString(Utility.JSON_NODE_UPDATED))); //set updated time
			
			/* set alternate url of the item*/
			if (!itemJsonObject.isNull(Utility.JSON_NODE_ALTERNATE)) {
				JSONObject alternate = itemJsonObject.getJSONArray(Utility.JSON_NODE_ALTERNATE).getJSONObject(0);
				item.setAlternateUrl(alternate.getString(Utility.JSON_NODE_HREF));
			}
			
			
			/*set content of the item*/
			if(!itemJsonObject.isNull(Utility.JSON_NODE_SUMMARY)){	//if it has summary node as the content
				JSONObject itemContent = itemJsonObject.getJSONObject(Utility.JSON_NODE_SUMMARY);
				item.setContent(itemContent.getString(Utility.JSON_NODE_CONTENT));
			}
			else{
				JSONObject itemContent = itemJsonObject.getJSONObject(Utility.JSON_NODE_CONTENT);
				item.setContent(itemContent.getString(Utility.JSON_NODE_CONTENT));
			}
			item.setSummary(Utility.trimString(HtmlHandler.htmlToText(item.getContent()), 150));
			
			if (itemJsonObject.isNull(Utility.JSON_NODE_AUTHOR)) {
				item.setAuthor("Unknown");
			}else{
				item.setAuthor(itemJsonObject.getString(Utility.JSON_NODE_AUTHOR)); //set author
			}
			
			/*last one in json object: origin, including feed id, title*/
			JSONObject origin = itemJsonObject.getJSONObject(Utility.JSON_NODE_ORIGIN);
			item.setFeedID(origin.getString(Utility.JSON_NODE_STREAMID));
			item.setFeedTitle(origin.getString(Utility.JSON_NODE_TITLE));
			
			//TODO: also some other nodes need to be handled, like: enclosure, mediagroup.
		} catch (JSONException e) {
			throw e;
		}
		SaveItem(item);
	}
	
	
	/**
	 * Parse ids info. return the ids in a set collection
	 * @param num numbers of the items we need
	 * @param ot we need to fetch item after this time
	 * @return a set collection contains ids
	 * @throws Exception BadAccountException
	 */
	public Set<Long> GetItemsIds(int num, long ot) throws Exception{
		String url = Utility.ITEM_IDS_URL;
		String xt = Utility.ITEM_READ_STATE;
		String output = "json";
		String s = Utility.ITEM_READINGLIST_STATE;
		String otString = ot + "";
		String nString = num + "";
		String ckString = System.currentTimeMillis()+"";
		String queryString = Utility.makeupParameter(Utility.PARAMETER_N,nString,
				Utility.PARAMETER_S,s,Utility.PARAMETER_XT,xt,Utility.PARAMETER_OT,otString,
				Utility.PARAMETER_CK,ckString,Utility.PARAMETER_OUTPUT,output);
		String auth = getAuth();
		Set<Long> ids = new HashSet<Long>();
		String responseString = "";
		try{
			responseString = GDataClient.doGet(url, queryString, auth);
		}
		catch (Exception e) {
			/* for auth token expiring
			 * TODO to be checked.*/
			if (e instanceof HttpUnauthorizedException) {
				auth = getANewAuth();
				
				responseString = GDataClient.doGet(url, queryString, auth);
			}
			else{
				throw e;
			}
		}
		JSONObject jsonContent = new JSONObject(responseString);
		JSONArray jsonArray = jsonContent.getJSONArray(Utility.JSON_NODE_ITEMREFS);
		int itemsCount = jsonArray.length();
		/* save ids into the set to be returned.*/
		for(int i=0; i<itemsCount; i++){
			JSONObject itemId = jsonArray.getJSONObject(i);
			Long id = Long.valueOf(itemId.getString(Utility.JSON_NODE_ID));
			//String idString = "tag:google.com,2005:reader/item/" + Long.toHexString(id);
			ids.add(id);
		}
		return ids;
	}
	
	
	
	/**
	 * save new item into database.
	 * @param item new item.
	 */
	private void SaveItem(Item item){
		/* put item into content values.*/
		ContentValues itemValues = new ContentValues();
		itemValues.put(FeedData.Items.ALTERNATE_URL, item.getAlternateUrl());
		itemValues.put(FeedData.Items.ATOM_ID, item.getAtomID());
		itemValues.put(FeedData.Items.AUTHOR, item.getAuthor());
		itemValues.put(FeedData.Items.CONTENT, item.getContent());
		itemValues.put(FeedData.Items.SUMMARY, item.getSummary());
		itemValues.put(FeedData.Items.FEED_ID, item.getFeedID());
		itemValues.put(FeedData.Items.FEED_TITLE, item.getFeedTitle());
		itemValues.put(FeedData.Items.LIKED_STATE, item.getLikeState());
		itemValues.put(FeedData.Items.LIKED_STATE_PENDING, item.getLikeStatePending());
		itemValues.put(FeedData.Items.READ_STATE, item.getReadState());
		itemValues.put(FeedData.Items.READ_STATE_PENDING, item.getReadStatePending());
		itemValues.put(FeedData.Items.SHAREED_STATE, item.getShareState());
		itemValues.put(FeedData.Items.SHAREED_STATE_PENDING, item.getShareStatePending());
		itemValues.put(FeedData.Items.STARRED_STATE, item.getStarredState());
		itemValues.put(FeedData.Items.STARRED_STATE_PENDING, item.getStarredStatePending());
		itemValues.put(FeedData.Items.TITLE, item.getTitle());
		itemValues.put(FeedData.Items.UPDATE_TIMESTAMP, item.getUpdateTimestamp());
		
		Uri uri = context.getContentResolver().insert(FeedData.Items.CONTENT_URI, itemValues);
		int itemId = getIdInUri(uri);
		
		String feedId = item.getFeedID();
		Feed feed = getFeed(feedId);
		/* save the new feed.*/
		if (feed == null) {
			Feed newFeed = new Feed();
			newFeed.setAtomID(feedId);
			newFeed.setTitle(item.getFeedTitle());
			saveFeed(newFeed);
		}
		
		List<String> categories = item.categories;
		for (String category : categories) {
			Label label = getLabel(category);
			/*save new label*/
			int labelId;
			if (label == null) {
				Label newLabel = new Label();
				newLabel.setName(category);
				labelId = saveLabel(newLabel);
			}else {
				labelId = label.get_id();
			}
			/*save item label relation*/
			saveItemLabel(itemId, labelId);
		}
		
		
	}
	
	/**
	 * save feed to db.
	 * @param feed
	 * @return the new feed's _id
	 */
	private int saveFeed(Feed feed){
		ContentValues feedValues = new ContentValues();
		feedValues.put(FeedData.Feeds.ATOM_ID, feed.getAtomID());
		feedValues.put(FeedData.Feeds.TITLE, feed.getTitle());
		feedValues.put(FeedData.Feeds.SORT_ID, feed.getSortID());
		
		Uri uri = context.getContentResolver().insert(FeedData.Feeds.CONTENT_URI, feedValues);
		return getIdInUri(uri);
	}
	
	/**
	 * save label to db.
	 * @param label
	 * @return the new label's _id
	 */
	private int saveLabel(Label label){
		ContentValues labelValues = new ContentValues();
		labelValues.put(FeedData.Labels.NAME, label.getName());
		labelValues.put(FeedData.Labels.SORT_ID, label.getSortID());
		
		Uri uri = context.getContentResolver().insert(FeedData.Labels.CONTENT_URI, labelValues);
		return getIdInUri(uri);
	}
	
	/**
	 * save itemlabel to db.
	 * @param itemId
	 * @param labelId
	 * @return the new itemlabel's _id
	 */
	private int saveItemLabel(int itemId, int labelId){
		ContentValues values = new ContentValues();
		values.put(FeedData.Item_label.ITEM_ID, itemId);
		values.put(FeedData.Item_label.LABEL_ID, labelId);
		
		Uri uri = context.getContentResolver().insert(FeedData.Item_label.CONTENT_URI, values);
		return getIdInUri(uri);
	}
	
	/**
	 * get a feed by feedId
	 * @param feedId the atom id of the feed
	 * @return
	 */
	private Feed getFeed(String feedId){
		Feed feed = new Feed();
		String selection = FeedData.Feeds.ATOM_ID + "=\"" + feedId +"\"";		
		Cursor c = context.getContentResolver().query(FeedData.Feeds.CONTENT_URI, null, selection, null, null);
		if (c.getCount()>0 && c.moveToFirst()) {
			feed.set_id(c.getInt(c.getColumnIndex(FeedData.Feeds._ID)));
			feed.setAtomID(c.getString(c.getColumnIndex(FeedData.Feeds.ATOM_ID)));
			feed.setTitle(c.getString(c.getColumnIndex(FeedData.Feeds.TITLE)));
			feed.setSortID(c.getString(c.getColumnIndex(FeedData.Feeds.SORT_ID)));
			c.close();
			return feed;
		}
		c.close();
		return null;
	}
	
	/**
	 * get a label by label name.
	 * @param name the label name.
	 * @return
	 */
	private Label getLabel(String name){
		Label label = new Label();
		String[] projection = new String[]{
				FeedData.Labels._ID,
				FeedData.Labels.NAME,
				FeedData.Labels.SORT_ID
		};
		String selection = FeedData.Labels.NAME + "=\"" + name + "\"";
		Cursor c = context.getContentResolver().query(FeedData.Labels.CONTENT_URI, 
				projection, selection, null, null);
		if (c.getCount()>0 && c.moveToFirst()) {
			label.set_id(c.getInt(c.getColumnIndex(FeedData.Labels._ID)));
			label.setName(c.getString(c.getColumnIndex(FeedData.Labels.NAME)));
			label.setSortID(c.getString(c.getColumnIndex(FeedData.Labels.SORT_ID)));
			c.close();
			return label;
		}
		c.close();
		return null;
	}
	
	/**
	 * get a label by label id
	 * @param id
	 * @return
	 */
	private Label getLabel(int id){
		Label label = new Label();
		String[] projection = new String[]{
				FeedData.Labels._ID,
				FeedData.Labels.NAME,
				FeedData.Labels.SORT_ID
		};
		String selection = FeedData.Labels._ID + "=" + id;
		Cursor c = context.getContentResolver().query(FeedData.Labels.CONTENT_URI, 
				projection, selection, null, null);
		if (c.getCount()>0 && c.moveToFirst()) {
			label.set_id(c.getInt(c.getColumnIndex(FeedData.Labels._ID)));
			label.setName(c.getString(c.getColumnIndex(FeedData.Labels.NAME)));
			label.setSortID(c.getString(c.getColumnIndex(FeedData.Labels.SORT_ID)));
			c.close();
			return label;
		}
		c.close();
		return null;
	}
	
	
	/**
	 * get item ids by label name
	 * @param name
	 * @return a list of _id of item
	 */
	private List<Integer> getItemIdsByLabel(String name){
		List<Integer> ids = new ArrayList<Integer>();
		Label label = getLabel(name);
		String selection = FeedData.Item_label.LABEL_ID + "=" + label.get_id();
		String[] projection = new String[]{
			FeedData.Item_label.ITEM_ID	
		};
		Cursor cursor = context.getContentResolver().query(FeedData.Item_label.CONTENT_URI, 
				projection, selection, null, null);
		if (cursor.getCount()>0 && cursor.moveToFirst()) {
			int id = cursor.getInt(cursor.getColumnIndex(FeedData.Item_label.ITEM_ID));
			ids.add(id);			
		}
		cursor.close();
		return ids;
	}
	
	/**
	 * sync the item changed state to server. the success one will be deleted.
	 * @throws Exception
	 */
	public void SyncItemState() throws Exception{
		List<Item> items = new ArrayList<Item>();
		String[] projection = new String[]{
				FeedData.Items._ID,
				FeedData.Items.ATOM_ID,
				FeedData.Items.READ_STATE,
				FeedData.Items.READ_STATE_PENDING,
				FeedData.Items.SHAREED_STATE,
				FeedData.Items.SHAREED_STATE_PENDING,
				FeedData.Items.STARRED_STATE,
				FeedData.Items.STARRED_STATE_PENDING,
				FeedData.Items.LIKED_STATE,
				FeedData.Items.LIKED_STATE_PENDING,
				FeedData.Items.FEED_ID,
				FeedData.Items.UPDATE_TIMESTAMP
		};
		String selection = FeedData.Items.READ_STATE + "<>" + FeedData.Items.READ_STATE_PENDING + " or " + 
			FeedData.Items.SHAREED_STATE + "<>" + FeedData.Items.SHAREED_STATE_PENDING + " or " +
			FeedData.Items.STARRED_STATE + "<>" + FeedData.Items.STARRED_STATE_PENDING + " or " +
			FeedData.Items.LIKED_STATE + "<>" + FeedData.Items.LIKED_STATE_PENDING;
		/*get the items contains different state and pending state.*/
		Cursor cursor = context.getContentResolver().query(FeedData.Items.CONTENT_URI,
				projection, selection, null, null);
		if(cursor.getCount()>0 && cursor.moveToFirst()){
			do{
				Item item = new Item();
				item.set_id(cursor.getInt(cursor.getColumnIndex(FeedData.Items._ID)));
				item.setAtomID(cursor.getString(cursor.getColumnIndex(FeedData.Items.ATOM_ID)));
				item.setReadState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.READ_STATE)));
				item.setReadStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.READ_STATE_PENDING)));
				item.setShareState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.SHAREED_STATE)));
				item.setShareStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.SHAREED_STATE_PENDING)));
				item.setStarredState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.STARRED_STATE)));
				item.setStarredStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.STARRED_STATE_PENDING)));
				item.setLikeState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.LIKED_STATE)));
				item.setLikeStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.LIKED_STATE_PENDING)));
				item.setFeedID(cursor.getString(cursor.getColumnIndex(FeedData.Items.FEED_ID)));
				item.setUpdateTimestamp(cursor.getLong(cursor.getColumnIndex(FeedData.Items.UPDATE_TIMESTAMP)));
				items.add(item);
			}while(cursor.moveToNext());
		}
		cursor.close();
		if(items.size()!=0){
			String t = getToken();
			String auth = getAuth();
			
			for(Item item:items){				
				postItemState(item, auth, t);
				
			}
		}
	}
	
	/**
	 * get All items
	 * @return
	 */
	public List<Item> getItemsFromDatabase(){
		return getItemsFromDatabase(true);
	}
	
	/**
	 * get brief info of items from db.
	 * @return a list of item(brief info)
	 */
	public List<Item> getItemsFromDatabase(boolean unreadOnly){
		List<Item> items = new ArrayList<Item>();
		String[] projection = new String[]{
			FeedData.Items._ID,
			FeedData.Items.ATOM_ID,
			FeedData.Items.ALTERNATE_URL,
			FeedData.Items.AUTHOR,
			FeedData.Items.CONTENT,
			FeedData.Items.SUMMARY,
			FeedData.Items.FEED_ID,
			FeedData.Items.FEED_TITLE,
			FeedData.Items.LIKED_STATE,
			FeedData.Items.LIKED_STATE_PENDING,
			FeedData.Items.READ_STATE,
			FeedData.Items.READ_STATE_PENDING,
			FeedData.Items.SHAREED_STATE,
			FeedData.Items.SHAREED_STATE_PENDING,
			FeedData.Items.STARRED_STATE,
			FeedData.Items.STARRED_STATE_PENDING,
			FeedData.Items.TITLE,
			FeedData.Items.UPDATE_TIMESTAMP,
			FeedData.Items.PREVIEW_IMG
		};
		String selectionString = null;
		if (unreadOnly) {
			selectionString = FeedData.Items.READ_STATE_PENDING + "=" + 0;
		}
		Cursor cursor = context.getContentResolver().query(FeedData.Items.CONTENT_URI, 
				projection, selectionString, null, FeedData.Items.DEFAULT_SORT_ORDER);
		if(cursor.getCount()>0 && cursor.moveToFirst()){
			do{
				Item item = new Item();
				item.set_id(cursor.getInt(cursor.getColumnIndex(FeedData.Items._ID)));
				item.setAtomID(cursor.getString(cursor.getColumnIndex(FeedData.Items.ATOM_ID)));
				item.setAuthor(cursor.getString(cursor.getColumnIndex(FeedData.Items.AUTHOR)));
				item.setAlternateUrl(cursor.getString(cursor.getColumnIndex(FeedData.Items.ALTERNATE_URL)));
				item.setContent(cursor.getString(cursor.getColumnIndex(FeedData.Items.CONTENT)));
				item.setSummary(cursor.getString(cursor.getColumnIndex(FeedData.Items.SUMMARY)));
				item.setFeedID(cursor.getString(cursor.getColumnIndex(FeedData.Items.FEED_ID)));
				item.setFeedTitle(cursor.getString(cursor.getColumnIndex(FeedData.Items.FEED_TITLE)));
				item.setLikeState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.LIKED_STATE)));
				item.setLikeStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.LIKED_STATE_PENDING)));
				item.setReadState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.READ_STATE)));
				item.setReadStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.READ_STATE_PENDING)));
				item.setShareState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.SHAREED_STATE)));
				item.setShareStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.SHAREED_STATE_PENDING)));
				item.setStarredState(cursor.getInt(cursor.getColumnIndex(FeedData.Items.STARRED_STATE)));
				item.setStarredStatePending(cursor.getInt(cursor.getColumnIndex(FeedData.Items.STARRED_STATE_PENDING)));
				item.setTitle(cursor.getString(cursor.getColumnIndex(FeedData.Items.TITLE)));
				item.setUpdateTimestamp(cursor.getInt(cursor.getColumnIndex(FeedData.Items.UPDATE_TIMESTAMP)));
				item.setPreviewImg(cursor.getString(cursor.getColumnIndex(FeedData.Items.PREVIEW_IMG)));
				items.add(item);
			}while(cursor.moveToNext());
		}
		cursor.close();
		return items;
	}
	
	/**
	 * get a list of items in a feed
	 * @param feedId feed's atom id.
	 * @return
	 */
	public List<Item> getItemsByFeed(String feedId){
		List<Item> items = new ArrayList<Item>();
		String[] projection = new String[]{
				FeedData.Items._ID,
				FeedData.Items.ATOM_ID,
				FeedData.Items.AUTHOR,
				FeedData.Items.TITLE,
				FeedData.Items.ALTERNATE_URL
		};
		
		String selection = FeedData.Items.FEED_ID + "=\"" + feedId +"\"";
		
		Cursor cursor = context.getContentResolver().query(FeedData.Items.CONTENT_URI, 
				projection, selection, null, FeedData.Items.DEFAULT_SORT_ORDER);
		if (cursor.getCount()>0 && cursor.moveToFirst()) {
			do {
				Item item = new Item();
				item.set_id(cursor.getInt(cursor.getColumnIndex(FeedData.Items._ID)));
				item.setAtomID(cursor.getString(cursor.getColumnIndex(FeedData.Items.ATOM_ID)));
				item.setAuthor(cursor.getString(cursor.getColumnIndex(FeedData.Items.AUTHOR)));
				item.setTitle(cursor.getString(cursor.getColumnIndex(FeedData.Items.TITLE)));
				item.setAlternateUrl(cursor.getString(cursor.getColumnIndex(FeedData.Items.ALTERNATE_URL)));
				
				items.add(item);
			} while (cursor.moveToNext());
		}
		cursor.close();
		
		return items;
	}
	
	/**
	 * get the feeds list from a list of items
	 * @param items
	 * @return a list of feeds
	 */
	public List<Feed> getFeeds(List<Item> items){
		List<Feed> feeds = new ArrayList<Feed>();
		for (Item item : items) {
			String feedId = item.getFeedID();
			boolean isExisted = false;
			for (Feed feed : feeds) {
				/* a old feed already in the list*/
				if (TextUtils.equals(feedId, feed.getAtomID())) {
					isExisted = true;
					feed.items.add(item);
					break;
				}
			}
			/* a new feed to be saved in the list*/
			if (!isExisted) {				
				Feed feed = getFeed(feedId);
				feed.items.add(item);
				feeds.add(feed);
			}
		}
		return feeds;
	}
	
	/**
	 * get all feeds in db.
	 * @return
	 */
	public List<Feed> getFeeds(){
		List<Feed> feeds = new ArrayList<Feed>();
		String[] projection = new String[]{
				FeedData.Feeds.ATOM_ID,
				FeedData.Feeds.TITLE
		};
		
		Cursor c = context.getContentResolver().query(FeedData.Feeds.CONTENT_URI, 
				projection, null, null, FeedData.Feeds.DEFAULT_SORT_ORDER);
		if (c.getCount()>0 && c.moveToFirst()) {
			do {
				Feed feed = new Feed();
				feed.setAtomID(c.getString(c.getColumnIndex(FeedData.Feeds.ATOM_ID)));
				feed.setTitle(c.getString(c.getColumnIndex(FeedData.Feeds.TITLE)));
				
				feeds.add(feed);
			} while (c.moveToNext());
		}
		c.close();
		return feeds;
	}
	
	/**
	 * get all labels in db.
	 * @return
	 */
	public List<Label> getLabels(){
		List<Label> labels = new ArrayList<Label>();
		String[] projection = new String[]{
				FeedData.Labels._ID,
				FeedData.Labels.NAME
		};
		Cursor cursor = context.getContentResolver().query(FeedData.Labels.CONTENT_URI, 
				projection, null, null, FeedData.Labels.DEFAULT_SORT_ORDER);
		if (cursor.getCount()>0 && cursor.moveToFirst()) {
			do {
				Label label = new Label();
				label.set_id(cursor.getInt(cursor.getColumnIndex(FeedData.Labels._ID)));
				label.setName(cursor.getString(cursor.getColumnIndex(FeedData.Labels.NAME)));
				
				labels.add(label);
			} while (cursor.moveToNext());
		}
		cursor.close();
		return labels;
	}
	
	/**
	 * get labels in a list of items
	 * @param items
	 * @return
	 */
	public List<Label> getLabels(List<Item> items){
		List<Label> labels = new ArrayList<Label>();
		String[] projection = new String[]{
			FeedData.Item_label.LABEL_ID
		};
		String selection = "1=1) group by(" + FeedData.Item_label.LABEL_ID;
		//select out the label_id
		Cursor cursor = context.getContentResolver().query(FeedData.Item_label.CONTENT_URI, 
				projection, selection, null, null);
		if (cursor.getCount()>0 && cursor.moveToFirst()) {
			 do {
				 //label_id
				int id = cursor.getInt(cursor.getColumnIndex(FeedData.Item_label.LABEL_ID));
				Label label = getLabel(id);
				//select out the item_id by label_id
				Cursor c = context.getContentResolver().query(FeedData.Item_label.CONTENT_URI, 
						new String[]{FeedData.Item_label.ITEM_ID}, 
						FeedData.Item_label.LABEL_ID + "=" + id, 
						null, null);
				if (c.getCount()>0 && c.moveToFirst()) {
					int length = items.size();
					List<Item> tItems = new ArrayList<Item>();
					do {
						//handle each item_id in label_id
						int itemId = c.getInt(c.getColumnIndex(FeedData.Item_label.ITEM_ID));
						for (int i = 0; i < length; i++) {
							Item item = items.get(i);
							if (item.get_id() == itemId) {
								tItems.add(item);
								//items.remove(i);
								break;
							}
						}						
					} while (c.moveToNext());
					List<Feed> feeds = getFeeds(tItems);
					label.feeds = feeds;
				}
				c.close();
				labels.add(label);
			} while (cursor.moveToNext());
		}

		cursor.close();
		return labels;
	}
	
	/**
	 * check the new ids if there is any item already existing in db. and remove the useless items in db.
	 * @param newIdSet
	 * @param oldItems
	 * @return the result item ids which need to request.
	 */
	public Set<Long> checkNewItemId(Set<Long> newIdSet, List<Item> oldItems){
//		Set<Long> resultIds = newIdSet;
		//Set<String> stringIds = new HashSet<String>();
//		for (Long idLong : newIdSet) {
//			stringIds.add(Utility.convertLongToItemId(idLong));
//		}
		for (Item item : oldItems) {
			String idString = item.getAtomID();
			long id = Utility.convertItemIdToLong(idString);
			//remove the duplicate id already existed in old items.
			if (newIdSet.contains(id)) {
				newIdSet.remove(id);
			}
			//the old item id not in the new id lists
			//delete it if states equal
			else {
				if (item.checkStateEqual()) {
					deleteItem(item);
				}
			}
		}
		return newIdSet;
	}
	
	/**
	 * delete an item from client.
	 * @param item
	 */
	public void deleteItem(Item item){
		String selection = FeedData.Item_label.ITEM_ID + "=" + item.get_id();
		context.getContentResolver().delete(FeedData.Item_label.CONTENT_URI, selection, null);
		context.getContentResolver().delete(
				Uri.withAppendedPath(FeedData.Items.CONTENT_URI, String.valueOf(item.get_id())), 
				null, null);
		deleteItemFile(item);
	}
	
	/**
	 * delete all data in database and files.
	 */
	public void clearCache(){
		context.getContentResolver().delete(FeedData.Item_label.CONTENT_URI, null, null);
		context.getContentResolver().delete(FeedData.Items.CONTENT_URI, null, null);
		context.getContentResolver().delete(FeedData.Feeds.CONTENT_URI, null, null);
		context.getContentResolver().delete(FeedData.Labels.CONTENT_URI, null, null);
		File dataDir = HtmlHandler.getDataDir(context);
		if (dataDir != null) {
			Utility.deleteFile(dataDir);
			dataDir.mkdirs();
		}
	}

	
	/**
	 * update an item's state in db.
	 * @param item
	 * @param stateName 
	 * @param stateValue
	 */
	public void updateItemState(Item item, String stateName, int stateValue){
		ContentValues values = new ContentValues();
		values.put(stateName, stateValue);
		String selection = FeedData.Items.ATOM_ID + "=\"" + item.getAtomID() + "\"";
		context.getContentResolver().update(FeedData.Items.CONTENT_URI, values, selection, null);		
	}
	
	public void updateItemState(Item item, String stateName, String stateValue){
		ContentValues values = new ContentValues();
		values.put(stateName, stateValue);
		String selection = FeedData.Items.ATOM_ID + "=\"" + item.getAtomID() + "\"";
		context.getContentResolver().update(FeedData.Items.CONTENT_URI, values, selection, null);
	}
	
	/**
	 * update an item's all states in db
	 * @param item
	 */
	public void updateItemState(Item item){
		updateItemState(item, 
				FeedData.Items.READ_STATE_PENDING, item.getReadStatePending());
		updateItemState(item, 
				FeedData.Items.SHAREED_STATE_PENDING, item.getShareStatePending());
		updateItemState(item, 
				FeedData.Items.LIKED_STATE_PENDING, item.getLikeStatePending());
		updateItemState(item, 
				FeedData.Items.STARRED_STATE_PENDING, item.getStarredStatePending());
	}
	
	private long GetLatestItemTimestamp(){
		SettingsPreference settingsPreference = new SettingsPreference(context);
		return settingsPreference.getLatestUpdatedTime();
	}
	
	private void SetLatestItemTimestamp(long timestamp){
		SettingsPreference settingsPreference = new SettingsPreference(context);
		settingsPreference.setLatestUpdatedTime(timestamp);
	}
	
	private int getIdInUri(Uri uri){
		int id = Integer.valueOf(uri.getPathSegments().get(1));
		return id;
	}
	
	/**
	 * get a valid but maybe expired auth token.
	 * @return
	 * @throws Exception
	 */
	private String getAuth() throws Exception{
		String auth = "";
		AccountPreference accountPreference = new AccountPreference(context);
		GoogleAccount account = accountPreference.getAccount();
		auth = account.get_auth();
		if (TextUtils.isEmpty(auth)) {
			auth = getANewAuth();
		}
		return auth;
	}
	
	/**
	 * get a new auth by recall Login() function
	 * @return auth token
	 * @throws Exception
	 */
	private String getANewAuth() throws Exception{
		AccountPreference accountPreference = new AccountPreference(context);
		GoogleAccount account = accountPreference.getAccount();
		account.Login();
		String auth = account.get_auth();
		accountPreference.setAccount(account);
		accountPreference.SaveAuth();
		return auth;
	}
	
	private String getToken() throws Exception{
		String token = "";
		AccountPreference accountPreference = new AccountPreference(context);
		GoogleAccount account = accountPreference.getAccount();
		token = account.get_token();
		if (TextUtils.isEmpty(token)) {
			token = getANewToken();
		}
		return token;
	}
	
	private String getANewToken()throws Exception{
		AccountPreference accountPreference = new AccountPreference(context);
		GoogleAccount account = accountPreference.getAccount();
		account.setGoogleToken();
		String token = account.get_token();
		accountPreference.setAccount(account);
		accountPreference.SaveToken();
		return token;
	}
	
	/**
	 * post an item's all state to change. post one time for one state. If all states post successfully, return true.
	 * @param item
	 * @param auth
	 * @param token
	 * @return
	 * @throws Exception
	 */
	private boolean postItemState(Item item, String auth, String token) throws Exception{
		boolean result = false;
		String addValue = null;
		String removeValue = null;
		if (item.getReadState() != item.getReadStatePending()) { //change read state.
			if(item.getReadStatePending() == 1){
				addValue = Utility.ITEM_READ_STATE;
				removeValue = Utility.ITEM_KEPTUNREAD_STATE;
			}else {
				addValue = Utility.ITEM_KEPTUNREAD_STATE;
				removeValue = Utility.ITEM_READ_STATE;
			}
			if(postItemState(item, auth, token, addValue, removeValue)){			
				item.toggleState("read");
				updateItemState(item, FeedData.Items.READ_STATE, item.getReadState());
			}
		}
		if (item.getLikeState() != item.getLikeStatePending()) { //change liked state
			if (item.getLikeStatePending() == 1) {
				addValue = Utility.ITEM_LIKED_STATE;
				removeValue = null;
				
			}else {
				addValue = null;
				removeValue = Utility.ITEM_LIKED_STATE;
				
			}
			if(postItemState(item, auth, token, addValue, removeValue)){
				item.toggleState("like");
				updateItemState(item, FeedData.Items.LIKED_STATE, item.getLikeState());
			}
		}
		if (item.getShareState() != item.getShareStatePending()) { //change share state
			if (item.getShareStatePending() == 1) {
				addValue = Utility.ITEM_BROADCAST_STATE;
				removeValue = null;
				
			}else {
				addValue = null;
				removeValue = Utility.ITEM_BROADCAST_STATE;
				
			}
			if(postItemState(item, auth, token, addValue, removeValue)){
				item.toggleState("share");
				updateItemState(item, FeedData.Items.SHAREED_STATE, item.getShareState());
			}
		}
		if(item.getStarredState() != item.getStarredStatePending()){ //change star state.
			if (item.getStarredStatePending() == 1) {
				addValue = Utility.ITEM_STARED_STATE;
				removeValue = null;
			}else {
				addValue = null;
				removeValue = Utility.ITEM_STARED_STATE;
			}
			if(postItemState(item, auth, token, addValue, removeValue)){
				item.toggleState("star");
				updateItemState(item, FeedData.Items.STARRED_STATE, item.getStarredState());
			}
		}
		result = item.checkStateEqual();
		if (result && item.getReadState() == 1) {
			deleteItem(item);
		}
		return result;
	}
	
	/**
	 * post one item state change.
	 * @param item
	 * @param auth
	 * @param token
	 * @param addValue the state value to add. (null for no adding)
	 * @param removeValue the state value to remove. (null for no removing)
	 * @return
	 * @throws Exception 
	 * 
	 */
	private boolean postItemState (Item item,String auth, String token, String addValue, String removeValue) throws Exception{
		boolean result = false;
		
		List<NameValuePair> params = Utility.makeupPostParameter(Utility.PARAMETER_T, token, 
				Utility.PARAMETER_I, item.getAtomID(), Utility.PARAMETER_S, item.getFeedID(),
				Utility.PARAMETER_A, addValue, Utility.PARAMETER_R,removeValue);
		try{
			GDataClient.makePost(Utility.EDIT_TAG_URL, params, auth);
			result = true;
		}catch (Exception e) {
			try{
				result = true;
				if(e instanceof HttpUnauthorizedException){ //handle expired auth. require both new auth and new token.
					auth = getANewAuth();
					params.remove(new BasicNameValuePair(Utility.PARAMETER_T, token));
					token = getANewToken();
					params.add(new BasicNameValuePair(Utility.PARAMETER_T, token));
					GDataClient.makePost(Utility.EDIT_TAG_URL, params, auth);
				}else if (e instanceof InvalidTokenException) {
					params.remove(new BasicNameValuePair(Utility.PARAMETER_T, token));
					token = getANewToken();
					params.add(new BasicNameValuePair(Utility.PARAMETER_T, token));
					GDataClient.makePost(Utility.EDIT_TAG_URL, params, auth);
				}else {
					throw e;
				}
			}catch (Exception ex) {
				result = false;
				Log.e(TAG, ex.getMessage());
				throw e;
			}
		}
		return result;
	}
	
	/**
	 * test if wifi connected
	 * @return
	 * if connected, return true; false otherwise
	 */
	public Boolean IsWifiConnected(){
		ConnectivityManager connectivityManager = 
			(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
		if(networkInfo!=null && networkInfo.isConnected()){
			return true;
		}
		else{
			return false;
		}
	}
	
	/**
	 * if your device has been connected to network
	 * @return
	 * true for connected; false otherwise
	 */
	public Boolean IsNetworkConnected(){
		ConnectivityManager connectivityManager = 
			(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
		if(networkInfo!=null && networkInfo.isConnected()){
			return true;
		}
		else{
			return false;
		}
	}
	
	/**
	 * download item files
	 * @param item
	 * @throws Exception
	 */
	public void downloadItem(Item item) throws Exception{
		HtmlHandler handler = new HtmlHandler(item, context);
		switch (item.getDownloadState()) {
		case 2:
			if (handler.resume()) {
				item.setDownloadState(1);
				updateItemState(item, FeedData.Items.DOWNLOAD_STATE, 1);
			}
			break;
		case 0:
			if (handler.start()) {
				item.setDownloadState(1);
				updateItemState(item, FeedData.Items.DOWNLOAD_STATE, 1);
			}else {
				item.setDownloadState(2);
				updateItemState(item, FeedData.Items.DOWNLOAD_STATE, 2);
			}
			break;
		default:
			break;
		}
		updateItemState(item, FeedData.Items.PREVIEW_IMG, item.getPreviewImg());
	}
	
	public void downloadItem(List<Item> items) throws Exception{
		for (Item item : items) {
			downloadItem(item);
		}
	}
	
	/**
	 * delete item file dir
	 * @param item
	 */
	public void deleteItemFile(Item item){
		HtmlHandler handler = new HtmlHandler(item, context);
		try {
			File itemDir = handler.getItemDir();
			Utility.deleteFile(itemDir);
		} catch (Exception e) {
			
		}
	}
	
	/**
	 * get the html file of the item, stored in SD card.
	 * @param item
	 * @return
	 */
	public File getItemDownloadFile(Item item){
		HtmlHandler handler = new HtmlHandler(item, context);
		File itemFile = null;
		
		try {
			File itemDir = handler.getItemDir();
			String fileName = Utility.parseItemId(item.getAtomID()) + ".html";
			itemFile = new File(itemDir, fileName);
			if (!itemFile.exists()) {
				itemFile = null;
			}
		} catch (Exception e) {
			
		}
		return itemFile;
	}
}
